// Copyright lowRISC contributors (OpenTitan project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#ifndef OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BOOTSTRAP_FUZZER_UTIL_H_
#define OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BOOTSTRAP_FUZZER_UTIL_H_

#include <iostream>
#include <stddef.h>
#include <stdint.h>

#include "absl/types/optional.h"
#include "absl/types/span.h"
#include "sw/device/silicon_creator/lib/drivers/mock_flash_ctrl.h"
#include "sw/device/silicon_creator/lib/drivers/mock_otp.h"
#include "sw/device/silicon_creator/lib/drivers/mock_rstmgr.h"
#include "sw/device/silicon_creator/lib/drivers/mock_spi_device.h"

namespace bootstrap_fuzzer {

/**
 * Consumes values on demand from a non-owning span.
 */
class StreamParser {
 public:
  /**
   * For speed, the constructor does not make a copy of `data`. Ensure that
   * `data` points to memory that outlives `this`.
   *
   * @param data Non-owning view into fuzzer-generated data.
   * @param verbose Whether verbose logging is enabled.
   */
  StreamParser(absl::Span<const uint8_t> data, bool verbose)
      : data_(data), verbose_(verbose) {}

  StreamParser(StreamParser &&) = default;

  StreamParser(StreamParser &) = delete;

  /**
   * Attempts to parse a `spi_device_cmd_t`. If there are not enough bytes, it
   * returns `default_value`. When `verbose_` is true, it describes the returned
   * value in a human-readable string written to stdout
   */
  spi_device_cmd_t ParseCmdOr(spi_device_cmd_t default_value) {
    const spi_device_cmd_t cmd = ParseCmd().value_or(default_value);
    if (verbose_) {
      std::cout << std::hex << "Parsed spi_device_cmd_t: {"
                << "opcode=0x" << cmd.opcode << ", address=0x" << cmd.address
                << ", payload_byte_count=0x" << cmd.payload_byte_count << "}"
                << std::endl;
    }
    return cmd;
  }

  /**
   * Attempts to parse an `IntType` from the stream. If there are not enough
   * bytes, it returns `default_value`. When `verbose_` is true, it uses
   * `log_label` to write a human-readable message to stdout.
   */
  template <typename IntType>
  IntType ParseIntOr(const char *log_label, IntType default_value) {
    const IntType value = ParseInt<IntType>().value_or(default_value);
    if (verbose_) {
      std::cout << std::hex << "Parsed " << log_label << ": 0x" << value
                << std::endl;
    }
    return value;
  }

 private:
  template <typename IntType>
  absl::optional<IntType> ParseInt() {
    static_assert(
        std::is_integral<IntType>::value || std::is_enum<IntType>::value,
        "IntType must be an integral or enum type");
    if (data_.length() < sizeof(IntType)) {
      return absl::nullopt;
    }
    IntType n;
    memcpy(&n, data_.data(), sizeof(IntType));
    data_ = data_.subspan(sizeof(IntType));
    return n;
  }

  absl::optional<spi_device_cmd_t> ParseCmd() {
    auto opcode = ParseInt<uint8_t>();
    auto address = ParseInt<uint32_t>();
    auto payload_byte_count = ParseInt<size_t>();
    if (!opcode || !address || !payload_byte_count) {
      return absl::nullopt;
    }
    const spi_device_cmd_t cmd{
        .opcode = static_cast<spi_device_opcode_t>(*opcode & UINT8_MAX),
        .address = *address,
        .payload_byte_count =
            *payload_byte_count % (kSpiDevicePayloadAreaNumBytes + 1),
    };
    return cmd;
  }

  absl::Span<const uint8_t> data_;
  bool verbose_{false};
};

/**
 * This class is responsible for one-time startup tasks. Instantiate it as a
 * static local variable in `LLVMFuzzerTestOneInput()`.
 */
class StaticFuzzerEnvironment {
 public:
  StaticFuzzerEnvironment();

  StaticFuzzerEnvironment(StaticFuzzerEnvironment &) = delete;
  StaticFuzzerEnvironment(StaticFuzzerEnvironment &&) = delete;
};

/**
 * This class configures mock objects with reasonable defaults. Some mocks will
 * enable the fuzzing engine to drive control flow by getting values from a
 * `StreamParser`.  Instantiate this class once per call to
 * `LLVMFuzzerTestOneInput()`.
 */
class AbstractBootstrapMockGroup {
 public:
  /**
   * @param data A chunk of data generated by the fuzzing engine.
   * @param verbose Whether to enable verbose logging.
   */
  AbstractBootstrapMockGroup(StreamParser stream, bool verbose);

  AbstractBootstrapMockGroup() = delete;
  AbstractBootstrapMockGroup(AbstractBootstrapMockGroup &) = delete;
  AbstractBootstrapMockGroup(AbstractBootstrapMockGroup &&) = delete;

  /**
   * This method configures the mocks owned by `AbstractBootstrapMockGroup`. If
   * you add custom mocks in a derived class and override this method, be sure
   * to call `AbstractBootstrapMockGroup::ConfigureMocks()`.
   */
  virtual void ConfigureMocks();

 protected:
  bool verbose_{false};
  bool flash_status_override_{false};
  size_t max_spi_cmd_count_{0};
  size_t spi_cmd_count_{0};
  StreamParser stream_;
  ::rom_test::NiceMockSpiDevice spi_device_;
  ::rom_test::NiceMockRstmgr rstmgr_;
  ::rom_test::NiceMockFlashCtrl flash_ctrl_;
  ::rom_test::NiceMockOtp otp_;
};

}  // namespace bootstrap_fuzzer

#endif  // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BOOTSTRAP_FUZZER_UTIL_H_
